Skip to content

Add liquid glass bottom tab bar to iOS, Android and mWeb - #101339

Open
sumo-slonik wants to merge 76 commits into
Expensify:mainfrom
software-mansion-labs:feat/liquid-glass-tab-bar
Open

sumo-slonik wants to merge 76 commits into
Expensify:mainfrom
software-mansion-labs:feat/liquid-glass-tab-bar

Conversation

@sumo-slonik

@sumo-slonik sumo-slonik commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Replaces the JS bottom tab bar on narrow layouts, changing its look on every platform:

  • iOS: native UITabBarController, rendered with liquid glass on iOS 26.
  • Android: native Material navigation bar in our colors.
  • Mobile web: JS bar redrawn as a flat floating capsule over the content.

The native bars come from createNativeBottomTabNavigator (@react-navigation/bottom-tabs/unstable) on top of Tabs from react-native-screens. Both are experimental APIs, so upgrading either package may break this.

iOS 26 ignores most tab item colors and paints every badge in the selected tab's color, so icons are drawn with Skia, each with its own status dot (on iOS also its label). Wide layouts keep the JS side bar.

Native bars fit 5 of our 6 tabs: with the Insights beta, Account moves to the top bar and opens full screen with a back button; without it, Insights has no item. A native tab tap follows the JS buttons' rules: Inbox opens at its chat list and anonymous users get sign-in.

Native tabs are mounted up front and not frozen, so a tab shows its skeleton as soon as it's picked, with no blank frame.

Patches (details in each details.md):

  • react-native-screens+4.28.0+001+animate-hiding-the-native-tab-bar: animates hiding and showing the iOS bar instead of a blink. The floating buttons fade with it.
  • react-native-screens+4.28.0+002+android-tab-label-typography: bold selected Android label, no Material letter spacing.
  • react-native-screens+4.28.0+003+no-android-tab-icon-tint: no icon tint on Android, so the avatar keeps its colors.
  • @react-navigation+bottom-tabs+7.16.2+002+active-indicator-color-precedence: fixes a bug that ignored tabBarActiveIndicatorColor on Android.
  • @react-navigation+bottom-tabs+7.16.2+003+hidden-tab-items: adds tabBarItemHidden for the sixth tab, and forwards tabBarAccessibilityLabel for VoiceOver.

@react-navigation/bottom-tabs is bumped to 7.16.2 and @react-navigation/native to 7.2.6, with their existing patches carried over.

Performance

Tab switching on iOS vs main (iPhone 17 simulator, iOS 27, dev build, 5 rounds). Average of median changes across all source tabs:

Tab First visit Later visits
Inbox -17% -41%
Spend -34% -46%

Android tab switching is also noticeably smoother with the native Material bar.

Fixed Issues

$ #101169
PROPOSAL: N/A

Tests

  1. Open the app and confirm the bottom bar: liquid glass on iOS 26, Material bar in our colors on Android, floating capsule on mobile web.
  2. Confirm unselected tab icons use the theme icon color and the selected tab is green, with a pill on Android and mobile web.
  3. With a status indicator on more than one tab (for example an unread report plus a workspace or account status), confirm each dot keeps its own color no matter which tab is selected.
  4. Open a report from the Inbox tab and go back: the tab bar should travel with the screen instead of blinking, and on native the floating buttons should fade with it.
  5. On iOS and Android, open a tab not visited yet in this session and confirm its skeleton appears immediately rather than an empty screen.
  6. With a report open in Inbox, switch to another tab and tap Inbox: it opens at the chat list.
  7. On iOS and Android with the Insights beta, open Account from the top bar: it opens full screen without the tab bar, and back returns to the previous tab.
  8. Open a workspace, switch to another tab and tap Workspaces: the workspace is restored, and another tap returns to the list.
  • Verify that no errors appear in the JS console

Offline tests

Same as the Tests section with the network turned off. None of these changes depend on network state.

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

Same as the Tests section.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Screen.Recording.2026-10-07.at.08.08.36.mov
Android: mWeb Chrome
iOS: Native
Screen.Recording.2026-10-06.at.22.39.56.mov
iOS: mWeb Safari
MacOS: Chrome / Safari

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

# Conflicts:
#	src/components/Navigation/NavigationTabBar/index.tsx
#	src/pages/Insights/InsightsPage.tsx
#	src/pages/home/HomePage.tsx
#	src/pages/inbox/sidebar/NavigationTabBarAvatar.tsx
Untint icons, label every tab, move visibility to screen options, tear down the color scheme coordinator.
The tab fragment is removed and never re-added on the pop. Also reset the color scheme coordinator.
The page skeleton stops at the first cached data and the list rendered a zero-sized view in the gap.
Android leaves the Material bar for the JS one; iOS paints its labels into the icon bitmaps.
# Conflicts:
#	src/pages/Insights/InsightsPage.tsx
#	src/pages/inbox/sidebar/SidebarLinks.tsx
Restore Android tab state, gate Insights on registered tabs, sum LHN inset.
Icons go to 20x20 with a tighter label gap; Android and mWeb blur the backdrop.
Adds a milky glass tint over a light blur.
@sumo-slonik sumo-slonik changed the title Add liquid glass bottom tabs to iOS Add liquid glass bottom tab bar to iOS, Android and mWeb Sep 24, 2026
@JmillsExpensify
JmillsExpensify self-requested a review September 24, 2026 10:53
… web

Drops the blur layer and expo-blur, and removes dead reveal and lazy-split code.
@melvin-bot
melvin-bot Bot requested review from grgia and removed request for a team October 7, 2026 06:02
@melvin-bot

melvin-bot Bot commented Oct 7, 2026

Copy link
Copy Markdown

@grgia Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

@MelvinBot review

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

@codex review

@MelvinBot

Copy link
Copy Markdown
Contributor

Code review

I found 2 likely bugs, 3 medium issues and a few smaller ones. The dependency bumps and patches look correct. I reviewed the code at 4c9148e and did not run it on a device, so please confirm the iOS label issues on a device or simulator.

Likely bugs

  1. iOS tab labels can't show non-Latin text. Labels are drawn with Skia drawText in one typeface, "Expensify Neue" (tabIconRasterizer.ts:77-81, :171). Skia drawText does not fall back to another font. The repo notes this font covers only Latin and emoji (multiFontFamily/index.ts:10-12). In Japanese or Chinese, the labels will probably show as empty boxes.
  2. iOS labels can be wider than their tab. The icon is as wide as the full label, with no width limit, truncation or wrapping (tabIconRasterizer.ts:145-148). Labels such as fr "Boîte de réception" or "Espaces de travail" are about 100pt wide, but each of 5 tabs gets about 70pt on a phone, so they overlap.

Medium

  1. Crossing the narrow/wide breakpoint remounts every tab. The wide branch wraps children in a different tree than the narrow branch (NativeTabLayout.tsx:42-57). Rotating or resizing a tablet loses each tab's scroll position and state, and reruns mount effects. Keep children in the same position in both layouts.
  2. Spend ignores the last saved search on wide native layouts. Tabs now mount at startup with Spend's default query. Only TabPressListeners swaps in the stored query (TabPressListeners.tsx:40-45), and it renders only on narrow layouts. A tablet that starts in landscape opens the default search instead of the last one.
  3. Bottom-pinned content can sit under the bar on iOS and mobile web. iOS tab roots drop bottom padding (useTabRootScreenWrapperProps/index.ios.tsx:11). On mobile web the bar is laid over the content (index.tsx:22-23). Only scrolling lists get extra room. This can hide the offline indicator and SearchSelectionFooter. Please check both with the bar visible.
Smaller issues
  • Screen readers no longer announce the status dot. The accessibility label is only the tab name (index.ios.ts:67, index.ts:57). The JS buttons add "Your review is required."
  • The avatar never loads again after a failed fetch. A failure changes none of the effect's inputs (useTabAvatarImage.ts:26-48). If you start the app offline, Account shows the generic icon until the next restart.
  • Android always reserves bar space. The spacer is added even when a blocking view hides the bar (index.android.tsx:23-27), which leaves an empty strip.
  • Old 72px bar height is still used. variables.bottomTabHeight remains in HomePage.tsx, SearchPageNarrow, emptyLHNWrapper and BaseEducationalTooltip.tsx, but none of the new bars is 72px tall.
  • iOS lists may get extra bottom space. contentInsetAdjustmentBehavior: 'automatic' already clears the home indicator. Lists that also add bottom safe-area padding end with about 34pt of blank space.
Checked and fine
  • Versions: bottom-tabs 7.16.2, native 7.2.6 and react-native-screens 4.28.0 match across package.json, the lockfile and node_modules. Peer deps are satisfied, with no duplicate copies.
  • Patches: every patch applies to the installed packages and is documented in details.md. The iOS hide-animation patch matches the upstream react-native-screens fix.
  • Tab presses: anonymous gating, the Inbox reset, the Workspaces restore and the Insights beta redirect all match the JS tab buttons. The redirect can't loop.
  • Icon cache: it is capped and disposes its Skia objects. Theme, locale and status changes redraw only the affected icons.
  • Other checks: every platform resolves a file, and Skia never loads on web. The PR adds no manual memoization, and the useSidebarOrderedReports change adds no LHN re-renders.

view run

JmillsExpensify
JmillsExpensify previously approved these changes Oct 7, 2026

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tested the latest adhoc. This is looking super great. Product approved.

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

Just tested the latest adhoc. This is looking super great. Product approved.

Great, it's really nice to hear that!

@JmillsExpensify

Copy link
Copy Markdown
Contributor

Assigned @ZhenjaHorbach for the C+ review. He'll start it soon.

Comment thread package.json Outdated
Comment on lines +124 to +127
"@react-navigation/bottom-tabs": "7.16.2",
"@react-navigation/core": "7.21.12",
"@react-navigation/material-top-tabs": "7.4.19",
"@react-navigation/native": "7.1.33",
"@react-navigation/native": "7.2.6",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using the newest versions of those two? We could also extract this into a separate PR, which proved to be the best approach for such complex and important changes. This way we can avoid reverts if there are any issues with the new versions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I extracted it into a separate PR: #103346. It bumps both to the newest versions that don't require a @react-navigation/core bump.

Comment thread src/styles/variables.ts Outdated
Comment on lines +32 to +33
const floatingTabBarHeight = 60;
const floatingTabBarBottomInset = 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are those two defined outside the export? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, already fixed.


type NativeTabLayoutProps = Parameters<NonNullable<NativeBottomTabNavigatorProps['layout']>>[0];

function NativeTabLayout({children, state, descriptors}: NativeTabLayoutProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely have to fix the offline indicator placement. Tested on web

Image

Comment on lines +52 to +61
<View style={styles.flex1}>
{children}
<TabPressListeners
state={state}
descriptors={descriptors}
/>
{!!isDebugModeEnabled && shouldShowNativeTabBar && <DebugTabView selectedTab={selectedTab} />}
{shouldShowNativeTabBar && (
// The shadow and the buttons belong to the bar, so they fade with it rather than appearing in place.
// They leave faster than they come in, so they stop covering a bar that is still sliding out.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could extract this entire tree to a separate component so wide layout don't pay for unnecessary hooks and onyx subscriptions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I already extracted it into NativeTabBarOverlay.

state={state}
descriptors={descriptors}
/>
{!!isDebugModeEnabled && shouldShowNativeTabBar && <DebugTabView selectedTab={selectedTab} />}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug mode looks kinda strange on narrow. Shouldn't the designs be adjusted a bit?

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can handle debug mode as a follow-up. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more like a design question, so summoning @Expensify/design

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Down to do debug stuff as a follow up. We can make that look a little nicer for sure, but I wouldn't block on it since it's not user facing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree
It's not a priority
But now it also looks a bit odd 😅

2026-10-08 09 51 33

descriptors={descriptors}
/>
{!!isDebugModeEnabled && shouldShowNativeTabBar && <DebugTabView selectedTab={selectedTab} />}
{shouldShowNativeTabBar && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch won't fire when we should use the narrow layout, and we shouldnt show native tab bar. So what exactly should happen in this scenario?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is basically Android file, right? Maybe we could rename it and make the index.ts no-op?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I already renamed it to index.android.ts.

* The native bar shows at most five of the six tabs. With the Insights beta, Insights takes the Account tab's place
* and Account moves to the top bar; without it, Insights has no item.
*/
function getTabWithoutBarItem(isInsightsBetaEnabled: boolean) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand that the account tab (with the beta on) is still a tab, even though it lives outside the bottom tab navigator? Why is that? Can't it be just a regular screen?

Comment on lines +77 to +80
const typeface = Skia.FontMgr.System().matchFamilyStyle(FontUtils.fontFamily.single.EXP_NEUE.fontFamily, {
weight: isBold ? FontWeight.Bold : FontWeight.Normal,
});
const font = Skia.Font(typeface, size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure that all languages are supported. I think Chinese could be affected and dont' display anything

- Reason: Two changes to the Android tab labels, both in `TabsAppearanceApplicator`, which React Navigation has no option for.
- Bold selected label. The Android tab bar takes one font weight for every label, while the design marks the selected tab with a bold label, the same way the JS side bar does. Material's `BottomNavigationView` draws each item with two labels, a small one shown while unselected and a large one shown while selected. `updateFontStyles` sets the large label's typeface with `Typeface.BOLD`, the way Material applies its own bold, so Android picks the bold face of the same font family. Material's `setItemTextAppearanceActiveBoldEnabled` is not used for this: it re-applies Material's 12sp text size to the large label alone and recomputes the selected item's offset from the difference between the two labels' sizes, so every selected tab would sit higher than the others.
- Letter spacing. `updateFontStyles` replaces the typeface and size of Material's tab labels but keeps the rest of Material 3's `LabelMedium` text appearance, which tracks letters 0.5sp apart. With Expensify Neue the labels read too spread out next to the rest of the app, so the patch sets the letter spacing of both labels to 0.
- Upstream PR/issue: not reported, because both changes are the App's typography (a bold selected label and its letter spacing) rather than a library defect, and RNScreens has no option for either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could add such options in an upstream PR? If not we'll have to support this patch forever

### [react-native-screens+4.28.0+003+no-android-tab-icon-tint.patch](react-native-screens+4.28.0+003+no-android-tab-icon-tint.patch)

- Reason: The Android account tab shows the user's avatar. `TabsAppearanceApplicator` assigns `bottomNavigationView.itemIconTintList` unconditionally, and a `ColorStateList` tint is `SRC_IN`, so it flattens the avatar to a solid silhouette in the tint color. React Navigation's `tinted: false` only reaches iOS, since `getPlatformIcon` in `@react-navigation/bottom-tabs` always hands Android a plain `imageSource`. The patch drops the icon tint list so Android draws every icon bitmap as supplied. The App recolors the glyphs off-screen in Skia for both selection states, so they keep the design's colors. Label colors are untouched and keep coming from `tabBarItemTitleFontColor`.
- Upstream PR/issue: not reported, because dropping the tint for every icon fits only a bar whose icons all arrive pre-colored, as the App's do. The disabled and focused icon colors go with it, which the App does not use.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, can't we add an option in react-native-screens?

const focusedRouteName = useNavigationState((state) => findFocusedRoute(state)?.name);
const navigation = useNavigation();
const isDrawnOverTabs = useIsSettingsDrawnOverTabs();
// The tab navigator keeps the full tab history, so going back returns to the tab the user opened Account from.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment
But let's add empty lines between blocks

Comment on lines +140 to +141
<Tab.Screen
name={NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these changes?

useNativeTabNavigator();
const {screenOptions, getTabOptions} = useNativeTabBarOptions({shouldShowNativeTabBar, isAccountAvatarShown, dotColors, tabLabels});
// A tab with no bar item draws no icon.
const getOptions = (name: NativeTabName) =>

@ZhenjaHorbach ZhenjaHorbach Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to pass name ourselves into every Tab.Screen

Maybe we can update getOptions to

const getOptions = ({route}: {route: {name: NativeTabName}}) => {
    const name = route.name;

    if (name === tabWithoutBarItem) {
        return HIDDEN_TAB_OPTIONS;
    }

    return {
        ...getTabOptions(name),
        tabBarSelectionEnabled: isNativeTabSelectionEnabled(name, {isAnonymousUser, isInboxAtChatList, isWorkspacesTabRestored}),
    };
};

As result we will have <Tab.Navigator screenOptions={getOptions}>

What do you think?

…he tab width, tabs keep their state across the breakpoint, wide Spend opens last search
…reenOptions, Android options file, offline indicator above the mWeb bar
…ithout core 7.21.13 typing getRootState as possibly undefined
dylanexpensify
dylanexpensify previously approved these changes Oct 7, 2026

@dylanexpensify dylanexpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from a product perspective 👍

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🚧 JmillsExpensify has triggered a test Expensify/App build. You can view the workflow run here.

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If the PR adds or modifies the UI:
    • I asked an AI agent to review the changes for accessibility issues and addressed its findings.
    • I tested with a screen reader (VoiceOver on macOS) and verified all new/changed elements are reachable with a logical focus order.
    • I verified all new/changed elements have meaningful accessible names and roles.
    • I verified state changes are announced (e.g. checked/unchecked, expanded/collapsed, selected).
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@sumo-slonik
Can you fix the conflicts, please?

@ZhenjaHorbach

ZhenjaHorbach commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Regarding hieroglyphs
With the new tab bar, the text isn't displaying correctly

(The Greek language is also broken)

telegram-cloud-photo-size-2-5348283334037019870-y

@ZhenjaHorbach

ZhenjaHorbach commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

And with long item texts, the tab bar also looks broken
For example, with Polish

2026-10-08 09 41 21

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants